package com.example.springbootelasticsearch.service.impl;

import cn.hutool.core.util.StrUtil;
import com.example.springbootelasticsearch.model.EsSight;
import com.example.springbootelasticsearch.model.SearchParam;
import com.example.springbootelasticsearch.service.SightService;
import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.lucene.search.function.FieldValueFactorFunction;
import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.GeoDistanceQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.*;
import org.elasticsearch.search.sort.GeoDistanceSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
 * @Description: 搜索
 * @Author zhangdongkang
 * @Date 2021/4/25
 */
@Service
public class SightServiceImpl implements SightService {

	@Autowired
	private ElasticsearchRestTemplate elasticsearchTemplate;

	/**
	 * @Description: 搜索
	 * @Author zhangdongkang
	 * @Date 2021/4/25
	 */
	@Override
	public Page<EsSight> search(SearchParam searchParam) {
		String keywords = searchParam.getKeywords();
		Integer pageNumber = searchParam.getPageNumber() == null ? 0 : searchParam.getPageNumber() - 1;
		Integer pageSize = searchParam.getPageSize();
		Integer distance = searchParam.getDistance();
		Double latitude = searchParam.getLatitude();
		Double longitude = searchParam.getLongitude();

		//分页
		PageRequest pageRequest = PageRequest.of(pageNumber, pageSize);

		//坐标
		Map<String, Double> origin = new HashMap<>();
		origin.put("lat", latitude);
		origin.put("lon", longitude);

		//查询条件
		FunctionScoreQueryBuilder.FilterFunctionBuilder[] filterFunctionBuilders = {
				//关键词匹配
				new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("sightName", keywords).operator(Operator.AND), ScoreFunctionBuilders.weightFactorFunction(3F)),
				new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("sightName.pinyin", keywords), ScoreFunctionBuilders.weightFactorFunction(1F)),
				new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("address", keywords), ScoreFunctionBuilders.weightFactorFunction(1F)),
				//距离打分规则，10km之内评分为1, 100km时评分为0.5，距离越远评分越低
				new FunctionScoreQueryBuilder.FilterFunctionBuilder(ScoreFunctionBuilders.gaussDecayFunction("position", origin, "100km", "10km").setWeight(1F)),
				//价格打分规则，0~30元之间评分为1, 100元时评分为0.5，价格越高评分越低
				new FunctionScoreQueryBuilder.FilterFunctionBuilder(ScoreFunctionBuilders.gaussDecayFunction("price", 15, 100, 15).setWeight(2F))
		};

		//查询对象
		FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(filterFunctionBuilders)
				.scoreMode(FunctionScoreQuery.ScoreMode.SUM)
				.boostMode(CombineFunction.SUM);

		//距离过滤，比如只查询10KM内的数据
		GeoDistanceQueryBuilder geoDistanceQueryBuilder = QueryBuilders.geoDistanceQuery("position")
				.point(latitude, longitude)
				.distance(distance, DistanceUnit.KILOMETERS)
				.geoDistance(GeoDistance.ARC);

		//距离排序
		GeoDistanceSortBuilder sortGeoBuilder = SortBuilders.geoDistanceSort("position", latitude, longitude)
				.unit(DistanceUnit.KILOMETERS)
				.order(SortOrder.ASC);

		NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
				.withQuery(functionScoreQueryBuilder)
				.withFilter(geoDistanceQueryBuilder)
				.withSorts(StrUtil.isEmpty(keywords) ? sortGeoBuilder : SortBuilders.scoreSort().order(SortOrder.DESC))
				.withPageable(pageRequest)
				.build();

		SearchHits<EsSight> searchHits = elasticsearchTemplate.search(searchQuery, EsSight.class);
		long totalHits = searchHits.getTotalHits();
		List<EsSight> list = searchHits.getSearchHits().stream().map(i -> {
			EsSight esSight = i.getContent();
			//计算距离
			double calculate = GeoDistance.ARC.calculate(latitude, longitude, esSight.getLatitude(), esSight.getLongitude(), DistanceUnit.KILOMETERS);
			esSight.setDistance(formatDistance(calculate));
			return esSight;
		}).collect(Collectors.toList());
		return new PageImpl<>(list, pageRequest, totalHits);
	}


	/**
	 * @Description 美化距离文本
	 * @Author zhangdongkang
	 * @Date 2022/2/8
	 */
	private String formatDistance(double distance) {
		if (distance > 1) {
			distance = (double) Math.round(distance * 100) / 100;
			return new StringBuilder().append(distance).append("km").toString();
		} else {
			distance = distance * 1000;
			return new StringBuilder().append((int) distance).append("m").toString();
		}
	}

}